import requests
from lxml import etree
import json
import subprocess
import os
from common.constant import ErrorInfo, RequestHeaders, DownloadType, XPath, CommonStrings
import re
from common.settings import ROOT_PATH, logger, 临时目录, 保存目录, 删除临时文件, 关闭浏览器
from .decorator_control import timer


class BilibiliHandler:

    def __init__(self, url: str = '', download_type: int = DownloadType.VIDEO.value, is_delete_cache: bool = True):
        self.url = url
        self.headers = {
            'referer': RequestHeaders.REFERER.value,
            'User-Agent': RequestHeaders.USER_AGENT.value,
            'cookie': RequestHeaders.COOKIE.value
        }
        self.video_url = ''
        self.audio_url = ''
        self.title = ''
        self.video_cache_file_path = ''
        self.audio_cache_file_path = ''
        self.download_type = download_type
        # 默认删除临时文件，如果想保留：is_delete_cache=False
        if 删除临时文件:
            self.is_delete_cache = is_delete_cache  # 删除临时文件
        else:
            self.is_delete_cache = False  # 保留临时文件

    def check_url(self) -> None:
        if not self.url:
            logger.error(ErrorInfo.URL_IS_NONE.value)
            raise ValueError(ErrorInfo.URL_IS_NONE.value)
        pattern = r'^https://www.bilibili.com/video/'
        if not re.match(pattern, self.url):
            logger.error(ErrorInfo.URL_IS_WRONG.value)
            raise ValueError(ErrorInfo.URL_IS_WRONG.value)

    def get_title_and_url(self) -> None:
        try:
            page = requests.get(url=self.url, headers=self.headers).text
        except Exception as e:
            logger.error(e)
            raise e
        tree = etree.HTML(page)
        self.title = tree.xpath(XPath.TITLE.value)[0]
        self.re_title()
        print(f'****获取到的标题 {self.title}')
        logger.info(f'title: {self.title}')

        script_text = tree.xpath(XPath.SCRIPT_TEXT.value)
        if not script_text:
            logger.error('地址错误：无法解析视频信息')
            raise ValueError('地址错误')
        play_info_str = str(script_text[0]).replace(CommonStrings.PLAY_INFO.value, '')
        try:
            play_info = json.loads(play_info_str)
        except json.JSONDecodeError:
            logger.error('地址错误：无效的视频信息')
            raise ValueError('地址错误')

        if self.download_type == DownloadType.VIDEO.value:
            # 检查视频流列表长度，默认使用索引为0的视频流（兼容性更好）
            video_streams = play_info['data']['dash']['video']
            if len(video_streams) > 2:
                self.video_url = video_streams[2]['baseUrl']  # AV1编码（画质更好）
            else:
                self.video_url = video_streams[0]['baseUrl']  # 默认编码
            self.audio_url = play_info['data']['dash']['audio'][0]['baseUrl']
            logger.info(f'\nvideo_url: {self.video_url}\naudio_url: {self.audio_url}')
        else:
            self.audio_url = play_info['data']['dash']['audio'][0]['baseUrl']
            logger.info(f'audio_url: {self.audio_url}')

    @timer
    def download_video_or_audio(self) -> None:
        if self.download_type == DownloadType.VIDEO.value:
            # self.video_cache_file_path = os.path.join(ROOT_PATH, 'cache', f'{self.title}-无音频.mp4')
            # self.audio_cache_file_path = os.path.join(ROOT_PATH, 'cache', f'{self.title}.mp3')
            self.video_cache_file_path = os.path.join(临时目录, f'{self.title}-无音频.mp4')
            self.audio_cache_file_path = os.path.join(临时目录, f'{self.title}.mp3')
            if not RequestHeaders.COOKIE.value:
                logger.warning(ErrorInfo.COOKIE_IS_NONE.value)
            try:
                logger.info(CommonStrings.DOWNLOADING.value)
                video = requests.get(url=self.video_url, headers=self.headers).content
                audio = requests.get(url=self.audio_url, headers=self.headers).content
            except Exception as e:
                logger.error(e)
                raise e
            with open(self.video_cache_file_path, 'wb') as fp:
                fp.write(video)
            logger.info(f'视频：{self.title}-无音频.mp4 下载完毕')
            with open(self.audio_cache_file_path, 'wb') as fp:
                fp.write(audio)
            logger.info(f'音频：{self.title}.mp3 下载完毕')
        elif self.download_type == DownloadType.AUDIO.value:
            # output_file_path = os.path.join(ROOT_PATH, 'output', f'{self.title}.mp3')
            output_file_path = os.path.join(保存目录, f'{self.title}.mp3')
            try:
                logger.info(CommonStrings.DOWNLOADING.value)
                audio = requests.get(url=self.audio_url, headers=self.headers).content
            except Exception as e:
                logger.error(e)
                raise e
            with open(output_file_path, 'wb') as fp:
                fp.write(audio)
            logger.info(f'音频：{self.title}.mp3 下载完毕')
        else:
            logger.error(ErrorInfo.DOWN_LOAD_TYPE_IS_WRONG.value)
            raise ValueError(ErrorInfo.DOWN_LOAD_TYPE_IS_WRONG.value)

    def compose_video_and_audio(self) -> None:
        # output_file_path = os.path.join(ROOT_PATH, 'output', f'{self.title}.mp4')
        output_file_path = os.path.join(保存目录, f'{self.title}.mp4')
        if self.download_type == DownloadType.VIDEO.value:
            if os.path.exists(self.video_cache_file_path):
                if os.path.exists(self.audio_cache_file_path):
                    # 让声音提前0.2秒，否则会音画不同步，声音比视频慢（具体调整时间因视频而异）
                    cmd = f'ffmpeg -i {self.video_cache_file_path} -itsoffset 0.2 -i {self.audio_cache_file_path} ' \
                          f'-c copy {output_file_path}'
                    logger.info(f'执行合成命令：{cmd}')
                    try:
                        subprocess.call(cmd, shell=True)
                        logger.info('音视频合成完毕')
                    except Exception as e:
                        logger.error(e)
                        raise e
                else:
                    logger.error(f'{self.audio_cache_file_path}不存在')
                    raise ValueError(f'{self.audio_cache_file_path}不存在')
            else:
                logger.error(f'{self.video_cache_file_path}不存在')
                raise ValueError(f'{self.video_cache_file_path}不存在')
        else:
            return None

    def remove_cache_file(self) -> None:
        if self.download_type == DownloadType.VIDEO.value and self.is_delete_cache:
            try:
                os.remove(f'{self.video_cache_file_path}')
                os.remove(f'{self.audio_cache_file_path}')
                logger.info('已删除临时文件')
            except Exception as e:
                logger.error(e)
                raise e
        else:
            return None

    def re_title(self) -> None:
        # 删除标题中的空格
        self.title = ''.join(self.title.split())
        # 用_替换标题中的特殊字符
        spe_char_lst = ['\'', '/', ':', '*', '?', '"', '<', '>', '|']
        for i in range(len(self.title)):
            for ch in spe_char_lst:
                if self.title[i] == ch:
                    self.title = self.title.replace(self.title[i], '_')
        self.title = self.title.replace(CommonStrings.REDUNDANT_STR.value, '')

    def run(self) -> None:
        logger.info('***** start *****')
        try:
            self.check_url()
            self.get_title_and_url()

            # 检查目标文件是否已存在
            j_wjm = os.path.join(保存目录, f'{self.title}.mp4')
            if os.path.exists(j_wjm):
                logger.info(f'文件已存在，跳过下载：{j_wjm}')
                return

            self.download_video_or_audio()
            self.compose_video_and_audio()
            self.remove_cache_file()
        except ValueError as e:
            if str(e) == '地址错误':
                logger.error('地址错误，跳过当前任务')
            else:
                raise e
        logger.info('***** end *****')
